Skip to content

Extract Spring Boot 2.x app source into nested-build subprojects#11408

Open
bric3 wants to merge 6 commits into
masterfrom
bdu/smoke-test-pattern-d-extract-applications
Open

Extract Spring Boot 2.x app source into nested-build subprojects#11408
bric3 wants to merge 6 commits into
masterfrom
bdu/smoke-test-pattern-d-extract-applications

Conversation

@bric3
Copy link
Copy Markdown
Contributor

@bric3 bric3 commented May 18, 2026

What Does This Do

For 12 smoke-test modules whose Spring Boot Gradle plugin is incompatible with Gradle 9, this PR extracts the application source into a self-contained application/ Gradle subproject and switches the outer module to the smokeTestApp { application { … } } DSL added in #11405.

Modules converted (bootJar):

  • springboot-thymeleaf (Spring Boot 2.7.15, Java 8)
  • springboot-freemarker (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
  • springboot-velocity (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
  • springboot-java-11 (Spring Boot 2.7.15, Java 11; passes iast-util-11 jar)
  • springboot-java-17 (Spring Boot 2.7.15, Java 17; passes iast-util-17 jar)
  • openfeature (Spring Boot 2.7.15, Java 11; passes feature-flagging-api jar)
  • kafka-2 (Spring Boot 2.7.15, Java 8; passes iast-util jar)
  • apm-tracing-disabled (Spring Boot 2.7.15, Java 8; passes dd-trace-api jar)

Modules converted (bootWar):

  • springboot-jpa (Spring Boot 2.6.0, Java 8; Lombok)
  • springboot-tomcat-jsp (Spring Boot 2.7.15, Java 8; JSP webapp)
  • springboot-jetty-jsp (Spring Boot 2.7.15, Java 8; JSP webapp)
  • springboot-tomcat (Spring Boot 2.5.12, Java 8; Ivy Tomcat download + unzip)

Tip

For each module the change is uniform:

  1. git mv src/main/ application/src/main/
  2. New application/settings.gradle + application/build.gradle carrying the Spring Boot plugin + the module's existing dependencies.
  3. Outer build.gradle rewritten to apply dd-trace-java.smoke-test-app and configure smokeTestApp { application { … } } (plus projectJar(name, project) for the 5 modules that forward a sibling jar).

Note

smokeTestApp has a convention of running Gradle 8.14.5, with a daemon running on Java 21, so it's not explicitly set. It's configurable if needed.

Motivation

The Spring Boot Gradle plugin pre-3.5.0 calls Configuration.getUploadTaskName(), removed in Gradle 9. These 12 modules block the root Gradle 9 migration.

Mirrors the goal of PR #11379 "Pattern C" (12 modules, source extraction), but instead of committing 22 per-application gradlew wrappers it uses the Gradle Tooling API via the dd-trace-java.smoke-test-app plugin (#11405). Net diff per module is smaller; nothing extra is committed to source control beyond the inner application/ Gradle scripts.

Additional Notes

  • Stacked on #11405 (smoke-test plugin infrastructure). Merge target is the infrastructure branch; rebase to master after Add smoke-test plugin for nested Gradle builds #11405 lands.
  • Per-module quirks:
    • kafka-2: spring-kafka-test is pinned to 2.8.11 in the outer test module (was previously resolved from the Spring Boot BOM, which no longer applies to the outer module).
    • springboot-freemarker / springboot-velocity: their XssController loads templates from resources/main/templates relative to the test JVM working directory. After the source move, the processed resources land under build/application/resources/main/. Each affected outer build adds a copyAppResources Copy task that mirrors them to build/resources/main/ so the runtime path resolves. jar dependsOn this Copy task to avoid an implicit-output conflict.
    • springboot-tomcat: the Ivy-downloaded Tomcat server (apache-tomcat-9.0.117) stays inside the nested build; the outer build forwards the unpack directory as a second system property via additionalSystemProperties.
    • apm-tracing-disabled: ApmTracingDisabledSamplingSmoke{V04,V1}Test are already @Flaky and continue to be skipped in CI.
  • Verified locally with ./gradlew :dd-smoke-tests:<module>:test -PskipFlakyTests=true for each module.

Contributor Checklist

🤖 Generated with Claude Code

Adds a new included build `build-logic/` hosting a single subproject
`smoke-test` that exposes the `dd-trace-java.smoke-test-app` plugin.

The plugin contributes:
- `NestedGradleBuild` task type that runs a nested Gradle build via the
  Gradle Tooling API. It pins the nested Gradle version (no committed
  per-application wrappers), uses the configured Java toolchain for the
  nested daemon, forwards artifact paths from the root build as
  `-P<name>=<path>`, and redirects the nested `buildDir` via
  `-PappBuildDir=<path>` so outputs land under the outer project's build
  directory.
- `smokeTestApp` project extension with an `application { ... }` block
  that registers the `NestedGradleBuild` task, wires it into every `Test`
  task via `dependsOn` + a `jvmArgumentProvider` for the produced
  artifact's system property. Consumers can also register
  `NestedGradleBuild` directly when they need more control; the plugin
  is a no-op until `application` or a manual registration is done.
- `projectJar(name, project)` helper that forwards a sibling project's
  jar to the nested build through a resolvable `Configuration` (avoids
  `evaluationDependsOn` and the cross-project access ordering issues).

The plugin is verified with JUnit 5 unit tests (`ProjectBuilder`) and
end-to-end tests that drive the Tooling API path through the Gradle Test
Kit with a temporary Kotlin-DSL test project.

`build-logic/settings.gradle.kts` references the existing
`gradle/libs.versions.toml` catalog (mirroring `buildSrc/`) so the
plugin can use the same library coordinates as the rest of the repo.
The Gradle libs Maven repository (`https://repo.gradle.org/gradle/libs-releases`,
scoped to `org.gradle:`) is added to the root build's `pluginManagement`
and to `gradle/repositories.gradle` so the Tooling API jar resolves.

Smoke-test modules with Spring Boot plugin versions incompatible with
Gradle 9 will use this plugin in follow-up PRs instead of a committed
Gradle 8 wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 added type: enhancement Enhancements and improvements tag: no release notes Changes to exclude from release notes comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM labels May 18, 2026
@bric3 bric3 changed the title chore(smoke-tests): extract Spring Boot 2.x app source into nested-build subprojects Extract Spring Boot 2.x app source into nested-build subprojects May 19, 2026
@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch from b924cb8 to 149a0ec Compare May 19, 2026 14:06
…14.5

Set conventions on `smokeTestApp`:
- `gradleVersion` defaults to `"8.14.5"` (Gradle 8 last release; pinned because
  Spring Boot plugin pre-3.5 calls `Configuration.getUploadTaskName()`, removed
  in Gradle 9).
- `javaLauncher` defaults to a JDK 21 toolchain (the version the root build
  requires for its own Gradle 9 migration; standardising the nested daemon on
  the same JDK avoids requiring an extra toolchain on dev machines and CI
  runners).

Consumers that need a different JDK or Gradle version still override
explicitly. The inner build script is responsible for pinning the produced
bytecode level (`java { sourceCompatibility = JavaVersion.VERSION_1_8 }` or
similar) — Gradle adds `--release N` automatically when source/target differs
from the daemon JVM.

`JavaToolchainService` is now injected into the extension; this works in any
project where a `java*` (or related) plugin is applied. Smoke-test modules
already apply `gradle/java.gradle`, which applies `java`, so the convention
resolves on first read.

Public defaults exposed as `DEFAULT_NESTED_GRADLE_VERSION` and
`DEFAULT_NESTED_JAVA_VERSION` constants so the values are discoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 force-pushed the bdu/smoke-test-plugin-infrastructure branch from c6d9744 to 1562192 Compare May 19, 2026 14:08
@datadog-prod-us1-4
Copy link
Copy Markdown

datadog-prod-us1-4 Bot commented May 19, 2026

Pipelines

Fix all issues with BitsAI

⚠️ Warnings

🚦 6 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-java | Ubuntu_20_amd64.SI94: [test-app-java-alpine]   View in Datadog   GitLab

🔧 Fix in code (Fix with Cursor). 1 failed test. AssertionError: _container_tags_validator failed to validate trace_id: 32080059661551732 in tests/auto_inject/utils.py:79

DataDog/apm-reliability/dd-trace-java | Ubuntu_20_amd64.SI94: [test-app-java-container]   View in Datadog   GitLab

🔧 Fix in code (Fix with Cursor). 1 failed test due to assertion failure in test_origin_detection. Error: _container_tags_validator failed to validate trace_id: 80543044647577393.

DataDog/apm-reliability/dd-trace-java | Ubuntu_23_10_arm64.SI94: [test-app-java-alpine]   View in Datadog   GitLab

🔧 Fix in code (Fix with Cursor). 1 failed test: AssertionError: _container_tags_validator failed to validate trace_id: 4897469160773231 in tests/auto_inject/test_auto_inject_install.py:255.

View all 6 failed jobs.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 5c3a181 | Docs | Datadog PR Page | Give us feedback!

@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch 2 times, most recently from 9e60b33 to 6364e58 Compare May 19, 2026 16:32
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: settings.gradle in nested projects have somewhat the same duplicated configuration around repo proxies. And ci cache. I have no proper solution yet, but this should come as a follow-up work.

@bric3 bric3 marked this pull request as ready for review May 19, 2026 16:41
@bric3 bric3 requested review from a team as code owners May 19, 2026 16:41
@bric3 bric3 requested review from claponcet, dd-oleksii, jandro996, leoromanovsky and ygree and removed request for a team May 19, 2026 16:41
…ugin

Switch the plugin sources and unit tests over to the typed
`org.gradle.kotlin.dsl` extension functions where they replace
`::class.java` boilerplate:

- `tasks.register(name, Type::class.java) { … }` → `tasks.register<Type>(name) { … }`
- `tasks.withType(Type::class.java).configureEach { … }` → `tasks.withType<Type>().configureEach { … }`
- `extensions.create("name", Type::class.java)` → `extensions.create<Type>("name")`
- `extensions.getByType(Type::class.java)` → `extensions.getByType<Type>()`
- `extensions.findByName("name")` (followed by `isInstanceOf`) → `extensions.findByType<Type>()`
- `project.plugins.apply(Plugin::class.java)` → `project.apply<Plugin>()` (PluginAware)
- `objects.newInstance(Type::class.java)` → `objects.newInstance<Type>()`

Also drop the six `captured*` local variables in `SmokeTestAppExtension.application` —
inside `tasks.register<NestedGradleBuild>(taskName) { … }` the outer extension's
properties are now reached via `this@SmokeTestAppExtension.<prop>` directly.

No behavioural change; the 9 plugin tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ild subprojects

The Spring Boot Gradle plugin is incompatible with Gradle 9 for all
versions before 3.5.0 because it calls `Configuration.getUploadTaskName()`,
a method removed in Gradle 9.

Twelve smoke-test modules were direct Gradle subprojects that applied the
Spring Boot plugin to build their bootJar / bootWar artefact. They
cannot stay as subprojects of the Gradle 9 root build.

For each of these modules, the application source is extracted into a
new `application/` subdirectory that is a fully self-contained Gradle
project (`settings.gradle` + `build.gradle`). The outer module keeps the
test source and delegates the application build to the
`dd-trace-java.smoke-test-app` plugin from `build-logic/` (added in the
parent infrastructure PR), which runs the nested build via the Gradle
Tooling API pinned to Gradle 8.14.5 — no committed `gradlew` wrapper.

Modules converted (bootJar):
- springboot-thymeleaf   (Spring Boot 2.7.15, Java 8)
- springboot-freemarker  (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
- springboot-velocity    (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
- springboot-java-11     (Spring Boot 2.7.15, Java 11; passes iast-util-11 jar)
- springboot-java-17     (Spring Boot 2.7.15, Java 17; passes iast-util-17 jar)
- openfeature            (Spring Boot 2.7.15, Java 11; passes feature-flagging-api jar)
- kafka-2                (Spring Boot 2.7.15, Java 8; passes iast-util jar)
- apm-tracing-disabled   (Spring Boot 2.7.15, Java 8; passes dd-trace-api jar)

Modules converted (bootWar):
- springboot-jpa         (Spring Boot 2.6.0, Java 8; Lombok)
- springboot-tomcat-jsp  (Spring Boot 2.7.15, Java 8; JSP webapp)
- springboot-jetty-jsp   (Spring Boot 2.7.15, Java 8; JSP webapp)
- springboot-tomcat      (Spring Boot 2.5.12, Java 8; Ivy Tomcat download + unzip)

Where an application module depends on a project artifact from the main
build (e.g. iast-util-11, dd-trace-api), the jar is forwarded via the
plugin's `projectJar(name, project)` helper — a resolvable
`Configuration` that establishes the upstream task dependency
automatically. The inner build picks it up via `findProperty(name)` and
adds it as `implementation files(...)`.

The `spring-kafka-test` test dependency in kafka-2 is pinned to 2.8.11
(the version previously resolved from the Spring Boot BOM) since the
outer test module no longer applies that BOM.

For springboot-freemarker and springboot-velocity, the `XssController`
loads templates from `resources/main/templates` relative to the test
JVM's working directory. After moving sources into `application/`, those
files land at `build/application/resources/main/templates/` instead of
`build/resources/main/templates/`. Each affected outer build registers a
`copyAppResources` Copy task that mirrors the inner build's processed
resources into the outer build dir so the runtime path still resolves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch from 6364e58 to 6b7e87c Compare May 19, 2026 17:00
Base automatically changed from bdu/smoke-test-plugin-infrastructure to master May 19, 2026 20:48
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot requested a review from a team as a code owner May 19, 2026 20:48
@gh-worker-ownership-write-b05516 gh-worker-ownership-write-b05516 Bot removed the request for review from a team May 19, 2026 23:48
Copy link
Copy Markdown
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, left minor comments and questions.
I feel that all build files should have more comments around why things done in that way, also maybe there is a way to minimize config by using reasonable defaults here and there.

Comment thread dd-smoke-tests/apm-tracing-disabled/application/build.gradle
Comment thread dd-smoke-tests/apm-tracing-disabled/application/settings.gradle
smokeTestApp {
application {
taskName = 'bootJar'
artifactPath = 'libs/apm-tracing-disabled-smoketest.jar'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as idea: how about to calculate default name of artifactPath from module name by template?
Like (pseudocode): libs/$moduleName-smoke-test.jar? With all needed .toLowerCase() and replace if needed. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I'll take a look in a follow up pr

Comment thread dd-smoke-tests/kafka-2/application/build.gradle
Comment thread dd-smoke-tests/kafka-2/application/build.gradle
Comment thread dd-smoke-tests/kafka-2/application/settings.gradle
Comment on lines +25 to +29
buildCache {
local {
directory = "$sharedRootDir/workspace/build-cache"
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's comment the knowledge behind such non-trivial-Gradle configs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note existing smoke tests already had this for quite some time, it just follows what's been there, it seems to have been introduced by b34ccbc, and has been applied to other smoke tests, prior my changes.

The buildcache dir config referes to the root level cache that was introduced in f6ec1f5 #982

I'll add the comments in a follow-up pr.

Comment on lines +19 to +22
java {
sourceCompatibility = 11
targetCompatibility = 11
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why Java 11 with old spring boot 2.7.15?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app was compiled with Java 11 before

tasks.named("compileJava", JavaCompile) {
configureCompiler(it, 11, JavaVersion.VERSION_11)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If app compiled with Java 11, maybe Gradle plugins can be bumped?

Comment on lines +24 to +27
def sharedRootDir = "$rootDir/../../../"
buildCache {
local {
directory = "$sharedRootDir/workspace/build-cache"
Copy link
Copy Markdown
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why in some projects sharedRootDir hardcoded, and some under isCI?

@bric3 bric3 added this pull request to the merge queue May 20, 2026
@dd-octo-sts
Copy link
Copy Markdown
Contributor

dd-octo-sts Bot commented May 20, 2026

/merge

@gh-worker-devflow-routing-ef8351
Copy link
Copy Markdown

gh-worker-devflow-routing-ef8351 Bot commented May 20, 2026

View all feedbacks in Devflow UI.

2026-05-20 07:05:46 UTC ℹ️ Start processing command /merge


2026-05-20 07:05:51 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-05-20 09:06:17 UTCMergeQueue: The build pipeline has timeout

The merge request has been interrupted because the build 0 took longer than expected. The current limit for the base branch 'master' is 120 minutes.

@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 20, 2026
Address review feedback on #11408: add comments explaining the legacy
Spring Boot 2.7.x / OpenTracing pins, the iastUtilJar property wired from
the outer build, the CI-only shared build cache (f6ec1f5 / #982,
b34ccbc), the internal Maven mirror proxies, and the Java 11 baseline
for the OpenFeature smoke test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bric3 added a commit that referenced this pull request May 20, 2026
Match the review feedback applied on #11408: expand the terse comments
on the nested settings.gradle files to explain the CI Maven mirror
proxies and the CI-only shared build cache (f6ec1f5 / #982,
b34ccbc), plus the Quarkus plugin version property forwarding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bric3 added a commit that referenced this pull request May 20, 2026
Match the review feedback applied on #11408: expand the terse comments
in the spring-boot-2.7-webflux nested settings.gradle to explain the CI
Maven mirror proxies and the CI-only shared build cache (f6ec1f5 /
#982, b34ccbc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
// root-level cache, and b34ccbc048 for the `isCI` gating — locally we keep the default
// per-user cache to avoid leaking entries into the repo tree.
if (isCI) {
def sharedRootDir = "$rootDir/../../../"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why in some places sharedRooDir is under if (CI) and in some places it is not?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the more I think about this, the more I think this build cache config is useless for nested smoke test projects. Given build cache is disabled by default and that the nested gradle jobs (existing or new via this PR) don't leverage the build-cache (there's neither --build-cache or org.gradle.caching=true). Consequently the buildCache { local { directory = … } } block is useless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: enhancement Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants